package com.feib.soeasy.job.importor;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.util.Assert;

import com.feib.soeasy.model.ColumnDefinition;
import com.feib.soeasy.model.ColumnValidateError;
import com.feib.soeasy.model.ExtraColumns;
import com.feib.soeasy.model.FormatDefinition;
import com.feib.soeasy.model.Payment;
import com.feib.soeasy.model.PaymentItem;
import com.feib.soeasy.model.UploadFile;
import com.feib.soeasy.job.service.ImportService;
import com.feib.soeasy.job.util.UnicodeInputStream;

/**
 * @title (#)FileChecker.java<br>
 * @description <br>
 * @author Jimmy Liu<br>
 * @version 1.0.0 2010/11/25
 * @copyright Far Eastern International Bank Copyright (c) 2010<br>
 * @2010/11/23 create by Jimmy Liu<br>
 */
public class FileChecker {

    private static String separator = "/";

    /**
     * 檔案編碼
     */
    private String charsetName = "MS950"; // or NULL to use systemdefault

    /**
     * 檔案來源
     */
    private File sourceFile;

    private UploadFile uploadFile;

    private FormatDefinition formatDefinition;

    private BufferedReader reader;

    private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");

    private List<ColumnValidateError> columnValidateErrors = new ArrayList<ColumnValidateError>(); // 錯誤紀錄

    private List<Payment> payments = new ArrayList<Payment>(); // 繳費單

    private boolean dontInsertPayment = false; // 因為有檢核錯誤(columnValidateErrors.size
                                               // > 0)，不需要新增繳費單到資料表中，若已新增的，則需要刪除

    private BaseValidatable customValidator;

    private ImportService importService;

    private String fileDownloadDestination;
    private Map<String, Long> Map = new HashMap<String, Long> (); 

    public FileChecker(UploadFile uploadFile, ImportService importService, String fileDownloadDestination) throws IllegalArgumentException, SecurityException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException {
        Assert.notNull(uploadFile, "uploadFile 不可以為NULL");

        this.uploadFile = uploadFile;
        this.sourceFile = new File(uploadFile.getFilePathOnServer());
        // 取得轉入格式定義檔
        this.formatDefinition = uploadFile.getFormatDefinition();
        Assert.notNull(formatDefinition, "formatDefinition 不可以為NULL");
        this.importService = importService;
        Assert.notNull(importService, "importService 不可以為NULL");

        // 客製化檢查
        if (null != formatDefinition.getCustomValidateClass()) {
            customValidator = (BaseValidatable) Class.forName(formatDefinition.getCustomValidateClass())
                            .getConstructor(UploadFile.class, FormatDefinition.class, List.class, ImportService.class)
                            .newInstance(uploadFile, formatDefinition, columnValidateErrors, importService);
        }
        else {
            customValidator = new BasicValidator(uploadFile, formatDefinition, columnValidateErrors, importService);
        }

        Assert.notNull(fileDownloadDestination, "fileDownloadDestination 不可以為NULL");

        this.fileDownloadDestination = fileDownloadDestination;
    }

    public boolean checkFile() throws UnsupportedEncodingException, IOException {
        try {

            // ================================================================//
            try {
                FileInputStream fis = new FileInputStream(this.sourceFile);
                UnicodeInputStream uin = new UnicodeInputStream(fis, this.charsetName);
                charsetName = uin.getEncoding(); // check and skip possible BOM
                                                 // bytes

                InputStreamReader isr;
                if (null == this.charsetName) {
                    isr = new InputStreamReader(uin);
                }
                else {
                    isr = new InputStreamReader(uin, this.charsetName);
                }

                reader = new BufferedReader(isr);
            }
            catch (FileNotFoundException e) {
                throw new RuntimeException("檔案位置錯誤(" + sourceFile.getAbsolutePath() + ")", e);
            }
            catch (UnsupportedEncodingException e) {
                throw new RuntimeException("系統不支援此編碼:" + this.charsetName, e);
            }

            ImportJob.logger.info("使用編碼{}拆解字串 ", this.charsetName);
            // ================================================================//

            List<ColumnDefinition> columnDefinitions = formatDefinition.getColumnDefinitions();
            int lineLength = 0;
            // 取 EndIndex 最大的為 每一行的總長度
            for (ColumnDefinition columnDefinition : columnDefinitions) {
                if (null != columnDefinition.getEndIndex())
                    lineLength = Math.max(lineLength, columnDefinition.getEndIndex().intValue());
            }
            ImportJob.logger.info("字串長度應為 {} bytes ", lineLength);

            Charset charset = Charset.forName(charsetName);
            // 只提供PropertyUtils.getPropertyType(new PaymentItem(), columnDefinition.getColumnName())使用，勿用於其他地方
            PaymentItem emptyPaymentItem = new PaymentItem(); 

            String lineData;
            byte[] lineBytes;
            long lineNumber = 0; // 行號
            long totalAmount = 0; // 每一繳費單金額累計

            // 逐行檢查
            while ((lineData = reader.readLine()) != null) {
                try {

                    lineNumber++;

                    lineBytes = lineData.getBytes(charsetName);

                    // step1 : 檢查長度，若長度不對，不用再往下檢查
                    if (lineLength != lineBytes.length) {
                        // 長度不不符
                        addColumnValidateError(lineNumber
                                        , null
                                        , "長度不符，應為[" + lineLength + "]，但實際為[" + lineBytes.length + "]", null);

                        // 此筆資料不需再做其他檢查，跳到下一筆資料
                        continue;
                    }

                    // step2 : 建立一筆新的繳費單，若檢核都通過，才可以新增到資料庫中
                    Payment payment = new Payment();
                    payment.setPaymentItems(new ArrayList<PaymentItem>());

                    Map<String, Integer> itemNameMap = new HashMap<String, Integer>(); // 明細欄位名稱的Index

                    boolean isConvertError = false; // 資料轉型是否有錯誤
                    boolean isBindingError = false; // 資料繫結至Payment物件是否有錯誤
                    boolean isValidateError = false;

                    for (ColumnDefinition columnDefinition : columnDefinitions) {
                        int beginIndex = 0; // 起始位置
                        int endIndex = 0; // 結束位置
                        isConvertError = false; // 資料轉型是否有錯誤
                        isBindingError = false; // 資料繫結至Payment物件是否有錯誤

                        // step3 : 將檔案上的字串轉成所設定的型態
                        String value = "";
                        Object convertedValue = null; // 轉行後的值, ex:
                                                      // 20101112->Date 物件
                        //2011/04/13 排除 BeginIndex EndIndex 為0項目
                        if (null == columnDefinition.getBeginIndex() || 0 == columnDefinition.getBeginIndex()) {
                            // 未定義
                            // do nothing
                        }else if( null == columnDefinition.getEndIndex() || 0 == columnDefinition.getEndIndex()) {
                            // 未定義
                            // do nothing
                        }else {

                            beginIndex = columnDefinition.getBeginIndex().intValue();
                            endIndex = columnDefinition.getEndIndex().intValue();
                            // 轉成 UTF-8字串

                            value = new String(new String(lineBytes, beginIndex - 1, endIndex - beginIndex + 1, charset).getBytes("UTF-8"), "UTF-8");
                            // 去空白
                            value = value.trim();
                        }

                        // Data Converting...
                        if (null != columnDefinition.getRequired() && columnDefinition.getRequired().booleanValue()) {
                            if (0 == value.length()) {
                                // 設定為必須輸入欄位，但是卻是空字串
                                addColumnValidateError(lineNumber
                                                , columnDefinition.getColumnTitle() + "(" + beginIndex + "-" + endIndex + ")"
                                                , "必須輸入欄位", null);

                                // 此筆資料不需再做其他檢查，跳到下一筆資料
                                continue;
                            }
                        }

                        if ("1".equals(columnDefinition.getColumnType())) {
                            // string
                            convertedValue = ((0 == value.length()) ? null : value);
                        }
                        else if ("2".equals(columnDefinition.getColumnType())) {
                            // number
                            try {
                                if (null != value && 0 != value.length()) {
                                    if (value.startsWith("+") || value.startsWith("-")) {
                                        boolean positive = value.startsWith("+");

                                        String tempValue = value.substring(1);

                                        convertedValue = Double.valueOf(tempValue);

                                        if (!positive) {
                                            convertedValue = new Double(0 - ((Double) convertedValue));
                                        }
                                    }
                                    else
                                        convertedValue = Double.valueOf(value);

                                    
                                    // add by Jimmy 20110413 for 轉型錯誤
                                    Class<Object> classType = null;
                                    if (columnDefinition.getColumnName().startsWith("attribute")) {
                                        // 自訂特殊欄位 應為字串
                                        classType = PropertyUtils.getPropertyType(new ExtraColumns(), columnDefinition.getColumnName());                                        
                                    }
                                    else {
                                        if (null != columnDefinition.getHeader() && columnDefinition.getHeader().booleanValue())
                                            classType = PropertyUtils.getPropertyType(payment, columnDefinition.getColumnName());
                                        else
                                            classType = PropertyUtils.getPropertyType(emptyPaymentItem, columnDefinition.getColumnName());
                                    }
                                    
                                    
                                        
                                    if (classType.equals(Long.class))
                                    {
                                        convertedValue = new Long(((Double)convertedValue).longValue());
                                    }
                                    else if (classType.equals(Integer.class))
                                    {
                                        convertedValue = new Integer(((Double)convertedValue).intValue());
                                    } else if (classType.equals(String.class))
                                    {
                                    	 convertedValue =new String((new Long(((Double)convertedValue).longValue())).toString());
                                    }

                                    // 累計總金額
                                    if (null != convertedValue && "totalAmount".equals(columnDefinition.getColumnName())) {
                                        totalAmount = totalAmount + ((Long) convertedValue).longValue();
                                    }
                                }
                            }
                            catch (Exception e) {
                                // 無法轉成數字
                                isConvertError = true;
                                addColumnValidateError(lineNumber
                                                , columnDefinition.getColumnTitle() + "(" + beginIndex + "-" + endIndex + ")"
                                                , "[" + (StringUtils.isEmpty(value) ? "空白" : value) + "] 不是數字格式", null);
                            }
                        }
                        else if ("3".equals(columnDefinition.getColumnType())) {
                            // date
                            try {
                                if (null != value && 0 != value.length())
                                    convertedValue = dateFormat.parse(value);
                            }
                            catch (Exception e) {
                                // 無法轉成日期
                                isConvertError = true;
                                addColumnValidateError(lineNumber
                                                , columnDefinition.getColumnTitle() + "(" + beginIndex + "-" + endIndex + ")"
                                                , "[" + (StringUtils.isEmpty(value) ? "空白" : value) + "] 不是日期格式(" + this.dateFormat.toPattern() + ")", null);
                            }
                        }
                        else {
                            throw new RuntimeException("欄位設定錯誤，應該只能是 1:String, 2:Number, 3:Date 其中之一");
                        }

                        // step4 : 將值放入對應的物件欄位中
                        // 表頭 或 明細欄位
                        boolean isHeader = (null != columnDefinition.getHeader() && columnDefinition.getHeader().booleanValue());
                        String columnName = columnDefinition.getColumnName();
                        if (isHeader) {
                            try {
                                if (columnDefinition.getColumnName().startsWith("attribute")) {
                                    // 自訂特殊欄位 應為字串
                                    if (null != convertedValue) {
                                        if (null == payment.getExtraColumns()) {
                                            ExtraColumns extraColumns = new ExtraColumns();
                                            extraColumns.setPayment(payment);
                                            payment.setExtraColumns(extraColumns);
                                        }
                                        PropertyUtils.setProperty(payment.getExtraColumns(), columnName, String.valueOf(convertedValue));
                                    }
                                }
                                else {
                                    PropertyUtils.setProperty(payment, columnName, convertedValue);
                                }
                            }
                            catch (Exception e) {
                                isBindingError = true;
                               
                               addColumnValidateError(lineNumber
                                                , columnDefinition.getColumnTitle() + "(" + beginIndex + "-" + endIndex + ")"
                                                , "型態錯誤，請檢查設定檔是否有錯！"
                                                , e);

                               /* throw new
                                RuntimeException("值注入物件時發生錯誤: 型態錯誤", e);*/
                            }
                        }
                        else {
                            // 依據欄位名稱依序放入明細
                            Integer listIndex = itemNameMap.get(columnName);
                            PaymentItem paymentItem = null;
                            if (null == listIndex) {
                                listIndex = new Integer(0);
                            }
                            else {
                                listIndex = new Integer(listIndex.intValue() + 1);
                            }

                            // 是否已經有明細了，若沒有 則新增一筆
                            if (payment.getPaymentItems().size() - 1 >= listIndex) {
                                paymentItem = payment.getPaymentItems().get(listIndex);
                            }
                            else {
                                paymentItem = new PaymentItem();
                                paymentItem.setPayment(payment);
                                paymentItem.setLoanable(Boolean.FALSE);
                                payment.getPaymentItems().add(listIndex.intValue(), paymentItem);
                            }

                            try {

                                if (columnDefinition.getColumnName().startsWith("attribute")) {
                                    // 自訂特殊欄位 應為字串
                                    if (null != convertedValue) {
                                        PropertyUtils.setProperty(paymentItem, columnName, String.valueOf(convertedValue));
                                    }
                                }
                                else {
                                    PropertyUtils.setProperty(paymentItem, columnName, convertedValue);
                                }
                            }
                            catch (Exception e) {
                                isBindingError = true;
                                throw new
                                 RuntimeException("值注入PaymentItem物件時發生錯誤: 型態錯誤",
                                 e);

                              /*  addColumnValidateError(lineNumber
                                                , columnDefinition.getColumnTitle() + "(" + beginIndex + "-" + endIndex + ")"
                                                , "型態錯誤，請檢查設定檔是否有錯！"
                                                , e);*/

                            }
                            itemNameMap.put(columnName, listIndex);
                        } // if (isHeader)

                    } // for(ColumnDefinition columnDefinition :
                      // columnDefinitions)

                    if (isConvertError || isBindingError) {
                        // 轉型物資料繫結有發生錯誤
                        // 此筆資料不需再做其他檢查，跳到下一筆資料
                        continue;
                    }

                    // step 4.5 phaseII 時候提出加入 通知訊息及注意事項2欄位，須轉換"換行"符號 "@#"
                    if (null != payment.getPaymentNote1())
                        payment.setPaymentNote1(StringUtils.replace(payment.getPaymentNote1(), "@#", "\r\n"));
                    if (null != payment.getPaymentNote2())
                        payment.setPaymentNote2(StringUtils.replace(payment.getPaymentNote2(), "@#", "\r\n"));
                    if (null != payment.getPaymentNote3())
                        payment.setPaymentNote3(StringUtils.replace(payment.getPaymentNote3(), "@#", "\r\n"));

                    // step5 : 一般基本檢核 與 帳單業者特殊檢核
                    try {
                        isValidateError = !this.customValidator.validate(payment, lineNumber);
                    }
                    catch (Exception e) {
                        isValidateError = false;
                        
                        addColumnValidateError(lineNumber
                                        , null
                                        , e.getMessage()
                                        , e);
                    }
                    
                    if (!isValidateError) {
                        // step6 : 檢核通過，將資料新增到資料庫中
                        addPayment(payment);
                    }

                    // 檢核錯誤就設定不需要在新增了
                    if (isValidateError) {
                        dontInsertPayment = true;
                    }

                    // free resource
                    if (null != itemNameMap) {
                        itemNameMap.clear();
                        itemNameMap = null;
                    }
                }
                catch (Exception e) {

                    ImportJob.logger.error("逐行檢查錯誤:", e);
                    addColumnValidateError(lineNumber, null, "系統錯誤！", e);

                    throw new RuntimeException(e);
                }
                finally {
                    // do nothing.
                }

            } // while((lineData = reader.readLine()) != null)

            // 總筆數

            uploadFile.setTotalItems(lineNumber);
            uploadFile.setTotalAmount(totalAmount);

            importService.updateUploadFileTotals(uploadFile);
        }

        catch (Exception e) {

            // passed = false;
            // dontInsertPayment = true;
            ImportJob.logger.error("檢核過程發生錯誤:", e);
            addColumnValidateError(0, null, "系統錯誤！", e);

        }

        finally {
            try {
                if (null != reader)
                    reader.close();
            }
            catch (IOException e) {
                // do nothing
            }
            reader = null;

            if (dontInsertPayment) {
                // 確保帳單都刪除
                deletePayments();

                // 將錯誤資料寫到資料表中
                flushColumnValidateErrors();
            }
            else {
                flushPayments();
            }
        }

        return !dontInsertPayment;
    }

    /**
     * 從資料庫中刪除繳費單
     */
    private void deletePayments() {
        importService.deletePaymentsByUploadFile(this.uploadFile);

    }

    /**
     * 新增繳費單
     * 
     * @param payment
     */
    protected void addPayment(Payment payment) {
        if (!dontInsertPayment) {
            for (int i = payment.getPaymentItems().size() - 1; i >= 0; i--) {
                if (StringUtils.isEmpty(payment.getPaymentItems().get(i).getItemName())
                                || null == payment.getPaymentItems().get(i).getAmount()) {//20110722 黃郁珊負數金額也要顯示
                    payment.getPaymentItems().remove(i);
                }
            }

            payment.setUploadFile(this.uploadFile);
            payment.setGroup(this.uploadFile.getGroup());
            payment.setMainBank(this.uploadFile.getGroup().getMainBank());
            payment.setPaymentStatus(Payment.PAYMENT_STATUS_WAIT_CONFIRM);
            /* 2011.12.08 中租客製化需求，同一合約編號，帳單檔名應該都一至 */
            if (payment.getGroup().getGroupType().intValue() == 4)
            {
                payment.setPdfFileName(payment.getVirtualAccount().substring(0, 14) + "01.pdf");

                // 繳費單(合併)下載目錄, ex:
                // c:/soeaxy/download/29092176001/bill/繳費期限8碼/繳費帳號前四碼/繳費帳號末三碼/
                String downloadDest = fileDownloadDestination + separator + this.uploadFile.getGroup().getGroupNo() + separator
                                    + "bill" + separator + this.dateFormat.format(payment.getPayDue()) + separator
                                    + payment.getVirtualAccount().substring(0, 4) + separator
                                    + payment.getVirtualAccount().substring(13,14) +"01"+ separator;
                File downloadFile = new File(downloadDest);
                // 目錄不存在則建立目錄
                if (!downloadFile.exists()) {
                    try {
                        downloadFile.mkdirs();
                    }
                    catch (Exception e) {
                    }
                }
                ImportJob.logger.debug("getVirtualAccount=="+payment.getVirtualAccount().substring(14, 16));
	            if (payment.getVirtualAccount().substring(14, 16).equals("01")){
	                Map.put(payment.getVirtualAccount().substring(0, 14), System.currentTimeMillis());
	            }
                String pdfFileName = payment.getVirtualAccount().substring(0, 14) + "01-" + Map.get(payment.getVirtualAccount().substring(0, 14))+ ".pdf";
                String pdfFilePathOnServer = downloadDest + pdfFileName;
                payment.setPdfFilePathOnServer(pdfFilePathOnServer);
                
            }
            else
            {
                /*維持原本邏輯*/
                payment.setPdfFileName(payment.getVirtualAccount() + ".pdf");

                // 繳費單(合併)下載目錄, ex:
                // c:/soeaxy/download/29092176001/bill/繳費期限8碼/繳費帳號前四碼/繳費帳號末三碼/
                String downloadDest = fileDownloadDestination + separator + this.uploadFile.getGroup().getGroupNo() + separator
                                    + "bill" + separator + this.dateFormat.format(payment.getPayDue()) + separator
                                    + payment.getVirtualAccount().substring(0, 4) + separator
                                    + payment.getVirtualAccount().substring(payment.getVirtualAccount().length() - 3) + separator;
                File downloadFile = new File(downloadDest);
                // 目錄不存在則建立目錄
                if (!downloadFile.exists()) {
                    try {
                        downloadFile.mkdirs();
                    }
                    catch (Exception e) {
                    }
                }
                String pdfFileName = payment.getVirtualAccount() + "-" + System.currentTimeMillis() + ".pdf";
                String pdfFilePathOnServer = downloadDest + pdfFileName;
                payment.setPdfFilePathOnServer(pdfFilePathOnServer);
            }


            this.payments.add(payment);

            if (payments.size() >= 50) {
                // 將繳費資料新增到資料庫中
                flushPayments();
                // 清除memory資料
                payments.clear();
            }
        }
    }

    /**
     * 
     */
    protected void flushPayments() {
        this.importService.addPayments(this.payments);

    }

    /**
     * 加入一筆檢核錯誤原因，若累積超過50筆資料，則整批新增到資料庫中
     * 
     * @param lineNumber 第幾行錯誤
     * @param columnName 錯誤欄位
     * @param errorMessage 錯誤原因
     * @param exceptionMessage 程式錯誤原因
     */
    protected void addColumnValidateError(long lineNumber, String columnName, String errorMessage, Exception exceptionMessage) {
        dontInsertPayment = true; // 不需要再新增繳費單，並且需要刪除已新增進去的資料(程式結束時刪即可)

        ColumnValidateError error = new ColumnValidateError();
        error.setUploadFile(uploadFile);
        error.setLineNumber(lineNumber);
        error.setColumnName(columnName);
        error.setErrorMessage(errorMessage);
        if (null != exceptionMessage)
            error.setExceptionMessage(exceptionMessage.getMessage());

        ImportJob.logger.debug(error.toString());
        columnValidateErrors.add(error);

        if (columnValidateErrors.size() >= 50) {
            // 將錯誤資料新增到資料庫中
            flushColumnValidateErrors();
            // 清除memory資料
            columnValidateErrors.clear();
        }
    }

    protected void flushColumnValidateErrors() {
        this.importService.addColumnValidateErrors(this.columnValidateErrors);
    }

}
